﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Utilities.Xna.Framework.Graphics;
using Utilities.Xna.Framework;
using System.Diagnostics;

namespace SceneManager.Processors
{
    public class LightProcessor : TextureProcessor
    {
        public List<Light> Lights { get; set; }
        public Color AmbientLight { get; set; }

        private Effect pointLightEffect;
        private Effect spotLightEffect;
        private Effect directionalLightEffect;

        private ComplexModel sphere;
        private ComplexModel cone;

        public LightProcessor(Game game, List<Light> lights, Color ambientLight) : base(game)
        {
            Lights = lights;
            AmbientLight = ambientLight;

            // Ajout des dépendances
            Inputs.Add("Normal", null);
            Inputs.Add("Depth", null);
            Inputs.Add("Color", null);

            // Création des textures Light et Specular
            Outputs.Add("Light", new RenderTarget2D(Game.GraphicsDevice, Width, Height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents));

            // Chargement des shaders
            pointLightEffect = Game.Content.Load<Effect>("Shaders/Lighting/PointLight");
            spotLightEffect = Game.Content.Load<Effect>("Shaders/Lighting/SpotLight");
            directionalLightEffect = Game.Content.Load<Effect>("Shaders/Lighting/DirectionalLight");

            // Chargement des models
            sphere = new ComplexModel(Game, "Models/LowSphere", Vector3.Zero, Vector3.Zero, Vector3.Zero, Materials.Default);
            cone = new ComplexModel(Game, "Models/LowCone", Vector3.Zero, Vector3.Zero, Vector3.Zero, Materials.Default);
        }

        public override void Process(Camera camera, GameTime gameTime)
        {
#if DEBUG
            stopwatch.Restart();
#endif
            Game.GraphicsDevice.SetRenderTarget(Outputs["Light"]);
            Game.GraphicsDevice.Clear(new Color(AmbientLight.R, AmbientLight.G, AmbientLight.B, 0));

            Vector3[] frustumCorners = camera.Frustum.GetCorners();
            for (int i = 0; i < 4; i++)
                frustumCorners[i] = frustumCorners[i + 4];

            Vector3 corner = frustumCorners[2];
            frustumCorners[2] = frustumCorners[3];
            frustumCorners[3] = corner;

            pointLightEffect.Parameters["CameraPosition"].SetValue(camera.Position);
            pointLightEffect.Parameters["FarPlane"].SetValue(camera.FarPlane);
            pointLightEffect.Parameters["FrustumCorners"].SetValue(camera.Frustum.GetCorners());
            pointLightEffect.Parameters["Normal"].SetValue(Inputs["Normal"]);
            pointLightEffect.Parameters["Depth"].SetValue(Inputs["Depth"]);
            pointLightEffect.Parameters["Color"].SetValue(Inputs["Color"]);

            spotLightEffect.Parameters["CameraPosition"].SetValue(camera.Position);
            spotLightEffect.Parameters["FarPlane"].SetValue(camera.FarPlane);
            spotLightEffect.Parameters["FrustumCorners"].SetValue(camera.Frustum.GetCorners());
            spotLightEffect.Parameters["Normal"].SetValue(Inputs["Normal"]);
            spotLightEffect.Parameters["Depth"].SetValue(Inputs["Depth"]);
            spotLightEffect.Parameters["Color"].SetValue(Inputs["Color"]);

            directionalLightEffect.Parameters["CameraPosition"].SetValue(camera.Position);
            directionalLightEffect.Parameters["FarPlane"].SetValue(camera.FarPlane);
            directionalLightEffect.Parameters["FrustumCorners"].SetValue(camera.Frustum.GetCorners());
            directionalLightEffect.Parameters["Normal"].SetValue(Inputs["Normal"]);
            directionalLightEffect.Parameters["Depth"].SetValue(Inputs["Depth"]);
            directionalLightEffect.Parameters["Color"].SetValue(Inputs["Color"]);

            #region PointLights

            Game.GraphicsDevice.BlendState = BlendState.Additive;
            foreach (PointLight light in Lights.OfType<PointLight>())
            {
                // FIXME
                BoundingSphere boundingSphere = new BoundingSphere(light.Position, light.Radius);
                if (boundingSphere.Contains(camera.Position) != ContainmentType.Disjoint)
                    Game.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
                else
                    Game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

                pointLightEffect.Parameters["LightPosition"].SetValue(light.Position);
                pointLightEffect.Parameters["LightColor"].SetValue(light.Color.ToVector4());
                pointLightEffect.Parameters["LightIntensity"].SetValue(light.Intensity);
                pointLightEffect.Parameters["LightRadius"].SetValue(light.Radius);

                sphere.Position = light.Position;
                sphere.Scale = new Vector3(light.Radius * 1.2f);
                sphere.Effect = pointLightEffect;
                sphere.Draw(camera, gameTime);
            }

            #endregion
            #region SpotLights

            Game.GraphicsDevice.BlendState = BlendState.Additive;
            foreach (SpotLight light in Lights.OfType<SpotLight>())
            {
                BoundingSphere boundingSphere = new BoundingSphere(light.Position, light.Radius);
                if (boundingSphere.Contains(camera.Position) == ContainmentType.Contains)
                    Game.GraphicsDevice.RasterizerState = RasterizerState.CullClockwise;
                else
                    Game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

                spotLightEffect.Parameters["LightPosition"].SetValue(light.Position);
                spotLightEffect.Parameters["LightColor"].SetValue(light.Color.ToVector4());
                spotLightEffect.Parameters["LightIntensity"].SetValue(light.Intensity);
                spotLightEffect.Parameters["LightRadius"].SetValue(light.Radius);
                spotLightEffect.Parameters["LightDirection"].SetValue(light.Direction);
                spotLightEffect.Parameters["LightAngle"].SetValue(light.Angle);

                // FIXME
                cone.Position = light.Position;
                cone.Rotation = Vector3Utils.GetQuaternion(Vector3.Up, light.Direction);

                float vScale = light.Radius;
                float hScale = (float)Math.Cos(light.Angle) * light.Radius * 1.2f;

                cone.Scale = new Vector3(hScale, vScale, hScale);
                cone.Effect = spotLightEffect;
                cone.Draw(camera, gameTime);
            }

            #endregion
            #region DirectionalLights

            Game.GraphicsDevice.BlendState = BlendState.Additive;
            foreach (DirectionalLight light in Lights.OfType<DirectionalLight>())
            {
                directionalLightEffect.Parameters["LightColor"].SetValue(light.Color.ToVector4());
                directionalLightEffect.Parameters["LightIntensity"].SetValue(light.Intensity);
                directionalLightEffect.Parameters["LightDirection"].SetValue(light.Direction);

                directionalLightEffect.CurrentTechnique.Passes[0].Apply();
                Game.GraphicsDevice.ComputeShader();
            }

            #endregion

#if DEBUG
            processTimes.Enqueue((int)stopwatch.ElapsedTicks);
            while (processTimes.Count > 60)
                processTimes.Dequeue();
#endif
        }
    }
}
